#include <iostream>
#include <list>
#include <map>
#include <string>

#include <cstring>

#include "tbb/task_scheduler_init.h"
#include "tbb/parallel_for.h"
#include "tbb/blocked_range2d.h"

#include "shell.hh"
#include "common.hh"

#include "mandelbrot.hh"
#include "julia.hh"

#include "grayscale.hh"
#include "revert.hh"
#include "bin.hh"
#include "binary_or.hh"
//#include "convolution.hh"

extern SDL_Surface* global_screen;

namespace shell
{
  using namespace std;
  
  typedef struct
  {
    string command;
    string arg1;
    string arg2;
    bool time;
  } Action;

  static void
  process(const list<Action>& actions,
	  map<string, Uint32*>& images,
	  unsigned nproc);

  static void
  process(const string& command,
	  Uint32* buffer1,
	  Uint32* buffer2,
	  bool time);

  void
  shell()
  {
    map<string, Uint32*> images;
    images.insert(pair<string, Uint32*>("screen", (Uint32*) global_screen->pixels));

    string input = "";
    Action action;
    unsigned nproc;

    while (input != "exit")
      {
	list<Action> actions;

	cout << "SHmelano> ";
	cin >> input;

	while ((input != "run") && (input != "exit"))
	  {
	    if (input == "time")
	      {
		action.time = true;
		cin >> input;
	      }
	    else
	      action.time = false;

	    action.command = input;
	    cin >> action.arg1;

	    if ((input == "copy") || (input == "binor"))
	      cin >> action.arg2;
	    else
	      action.arg2 = "";

	    actions.push_back(action);

	    cout << "SHmelano> ";
	    cin >> input;
	  }

	if (!actions.empty() && (input == "run"))
	  {
	    cin >> nproc;

	    tbb::task_scheduler_init init(nproc);

	    process(actions, images, nproc);

	    init.terminate();
	  }
      }
  }

  static void
  process(const list<Action>& actions,
	  map<string, Uint32*>& images,
	  unsigned nproc)
  {
    for (list<Action>::const_iterator it = actions.begin();
	 it != actions.end(); it++)
      {

	Uint32* buffer1 = 0;
	Uint32* buffer2 = 0;

	{ // BEGIN Getting or Creating the buffers to which the action appeals

	  map<string, Uint32*>::iterator img1;
	  img1 = images.find(it->arg1);
	  if (img1 == images.end())
	    {
	      buffer1 = new Uint32[HEIGHT * WIDTH];
	      images.insert(pair<string, Uint32*>(it->arg1, buffer1));
	    }
	  else
	    buffer1 = img1->second;
	
	  map<string, Uint32*>::iterator img2 = images.end();
	  if (it->arg2 != "")
	    {
	      img2 = images.find(it->arg2);
	      if (img2 == images.end())
		{
		  buffer2 = new Uint32[HEIGHT * WIDTH];
		  images.insert(pair<string, Uint32*>(it->arg2, buffer2));
		}
	      else
		buffer2 = img2->second;
	    }
	} // END

	process(it->command, buffer1, buffer2, it->time);
      }
  }

  static void
  process(const string& command,
	  Uint32* buffer1,
	  Uint32* buffer2,
	  bool time)
  {
    tbb::tick_count t0 = tbb::tick_count::now();

    if (command == "copy")
      memcpy(buffer1, buffer2, sizeof (Uint32) * HEIGHT * WIDTH);

    if (command == "display")
      {
	memcpy(global_screen->pixels, buffer1, sizeof (Uint32) * HEIGHT * WIDTH);
	SDL_UpdateRect(global_screen, 0, 0, 0, 0);
      }

    if (command == "red")
      {
	for (unsigned i = 0; i < HEIGHT; ++i)
	  for (unsigned j = 0; j < WIDTH; ++j)
	    buffer1[i * WIDTH + j] = SDL_MapRGB(global_screen->format, 255, 0, 0);
      }

    if (command == "green")
      {
	for (unsigned i = 0; i < HEIGHT; ++i)
	  for (unsigned j = 0; j < WIDTH; ++j)
	    buffer1[i * WIDTH + j] = SDL_MapRGB(global_screen->format, 0, 255, 0);
      }

    if (command == "blue")
      {
	for (unsigned i = 0; i < HEIGHT; ++i)
	  for (unsigned j = 0; j < WIDTH; ++j)
	    buffer1[i * WIDTH + j] = SDL_MapRGB(global_screen->format, 0, 0, 255);
      }

    if (command == "mandelbrot")
      tbb::parallel_for(tbb::blocked_range2d<int>(0, HEIGHT, 0, WIDTH),
			MandelbrotParrallel(buffer1),
			tbb::auto_partitioner());

    if (command == "grayscale")
      tbb::parallel_for(tbb::blocked_range2d<int>(0, HEIGHT, 0, WIDTH),
			GrayScale(buffer1),
			tbb::auto_partitioner());

    if (command == "julia")
      tbb::parallel_for(tbb::blocked_range2d<int>(0,HEIGHT, 0,WIDTH),
			JuliaParrallel(buffer1),
			tbb::auto_partitioner());

    if (command == "binor")
      tbb::parallel_for(tbb::blocked_range2d<int>(0,HEIGHT, 0,WIDTH),
			BinOr(buffer1, buffer2),
			tbb::auto_partitioner());

    if (command == "bin")
      tbb::parallel_for(tbb::blocked_range2d<int>(0,HEIGHT, 0,WIDTH),
			Bin(buffer1),
			tbb::auto_partitioner());    

    if (command == "revert")
      tbb::parallel_for(tbb::blocked_range2d<int>(0,HEIGHT, 0,WIDTH),
			Revert(buffer1),
			tbb::auto_partitioner());
      
    tbb::tick_count t1 = tbb::tick_count::now();

    if (time)
      cout << "time(" << command << "): " << (t1 - t0).seconds() << "s" << endl;
  }
}
